/*
 * Copyright (C) 2010 Fores Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.fores.midori.core.io;

import java.io.IOException;
import java.io.OutputStream;

/**
 * 対象を固定サイズのバイト配列だけに特化することにより、性能を向上したバイト配列出力ストリーム。<br>
 * このクラスは、Java標準の <code>java.io.ByteArrayOutputStream</code>クラスにおいて <code>toByteArray()</code> メソッドで結果のバイト配列を取得する際に必ず配列のコピーが行われてしまうため、
 * 無駄にメモリとCPUを消費してしまう現象を回避するために作成したクラスです。<br>
 * 内部にコンストラクタで指定された配列そのものを保持し、コピーは一切作成しません。<br>
 * Java標準の <code>java.io.ByteArrayOutputStream</code>クラスとは異なり、書き込むデータがバイト配列のサイズを超えた場合は、
 * バイト配列を拡張するのではなく例外を発生します。<br>
 * このクラスは、<code>flush()</code> メソッドや <code>close()</code> メソッドが呼び出されても何もしません。<br>
 * (このクラスは処理効率を重視するため、あえてスレッドセーフにしていません。)<br>
 */
public class FixedSizeByteArrayOutputStream extends OutputStream {
	//==========================================================
	//フィールド

	/**
	 * 書き込み対象のバイト配列
	 */
	protected final byte[] byteArray;

	/**
	 * 書き込み対象のバイト配列のサイズ
	 */
	//(byteArray.lengthで取得できる値だが、少しでも処理効率を上げるためにフィールドに保持しておく)
	protected final int byteArrayLength;

	/**
	 * 書き込み位置
	 */
	protected int position = 0;


	//==========================================================
	//メソッド

	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//コンストラクタ

	/**
	 * 書き込み対象のバイト配列のサイズを指定するタイプのコンストラクタです。<br>
	 *
	 * @param length 書き込み対象のバイト配列のサイズ
	 */
	public FixedSizeByteArrayOutputStream(int length) {
		//引数のサイズに応じたバイト配列を生成し、実際に処理を行うメソッドを呼び出す
		this(new byte[length]);
	}


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//オーバーライドするメソッド

	/**
	 * 書き込み対象のバイト配列そのものを指定するタイプのコンストラクタです。<br>
	 *
	 * @param byteArray 書き込み対象のバイト配列
	 */
	public FixedSizeByteArrayOutputStream(byte[] byteArray) {
		//==========================================================
		//引数の値をフィールドに設定

		//書き込み対象のバイト配列
		this.byteArray = byteArray;

		//書き込み対象のバイト配列のサイズ
		this.byteArrayLength = this.byteArray.length;
	}


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//オーバーライドするメソッド

	/**
	 * 指定されたバイトをこの出力ストリームに書き込みます。<br>
	 *
	 * @param b 対象のバイト
	 * @throws IOException 書き込み位置がすでにバイト配列の終端に達している場合
	 */
	@Override
	public void write(int b) throws IOException {
		//書き込み位置が書き込み対象のバイト配列のサイズ以上の場合
		//(書き込み位置がすでにバイト配列の終端に達している場合)
		if (this.position >= this.byteArrayLength) {
			//例外を投げる
			throw new IOException("書き込み位置がバイト配列の終端に達しました。");
		}

		//引数のバイトを書き込み対象のバイト配列に設定する
		this.byteArray[this.position] = (byte) b;

		//書き込み位置を1進める
		this.position++;
	}

	/**
	 * 指定されたバイト配列の、オフセット位置 off から始まる len バイトをこの出力ストリームに書き込みます。<br>
	 * このストリームがすでに閉じられている場合は、入出力例外を投げます。<br>
	 *
	 * @param b データの配列
	 * @param off データの開始オフセット
	 * @param len 書き込むバイト数
	 * @throws IOException 書き込むバイト数が書き込み対象のバイト配列の空き容量よりも大きい場合
	 * @throws NullPointerException b が null の場合
	 * @throws IndexOutOfBoundsException off が負の場合、len が負の場合、または len が b.length - off よりも大きい場合
	 */
	@Override
	public void write(byte b[], int off, int len) throws IOException {
		//==========================================================
		//親クラスと同じ入力チェックを行う

		//データの配列がnullの場合
		if (b == null) {
			//例外を投げる
			throw new NullPointerException("データの配列がnullです");
		}
		//off が負の場合、len が負の場合、または len が b.length - off よりも大きい場合
		else if (off < 0 || len < 0 || len > b.length - off) {
			//例外を投げる
			throw new IndexOutOfBoundsException("引数の値が不正です");
		}
		//書き込むバイト数が0の場合
		else if (len == 0) {
			//書き込み処理を行うまでもないので、以降の処理を行わない
			return;
		}
		//==========================================================


		//書き込むバイト数が書き込み対象のバイト配列の空き容量よりも大きい場合
		if (len > (this.byteArrayLength - this.position)) {
			//例外を投げる
			throw new IOException("書き込み位置がバイト配列の終端に達しました。");
		}

		//System.arraycopy()を使って、引数のデータを書き込み対象のバイト配列に一気にコピーする
		System.arraycopy(b, off, this.byteArray, this.position, len);

		//書き込み位置を書き込んだバイト数の分だけ進める
		this.position += len;
	}


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//通常のメソッド

	/**
	 * バイト配列を返します。<br>
	 *
	 * @return バイト配列
	 */
	public byte[] toByteArray() {
		//フィールドの値をそのまま返す
		return this.byteArray;
	}
}
